﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Effects.Effects.GuiParts;
using Effects.Effects.Tools;


namespace Effects.Effects
{
    
    /// <summary>
    ///  Delay effect.
    /// </summary>
    /// <remarks>
    ///  Delay is effect simulating echo. It delays input signal and mixes it with dry input. Dry/wet and delay is adjustable.
    ///  Last parameter is feedback. It tells how much of the delayed signal is to be delayed again (so it controls number of echos).
    /// </remarks>
    public class Delay:Effect
    {
        #region controls
        // fields and properties for user controlled parameters 

        #region footswitch
        /// <summary>
        ///  Footswitch for turning the effect On/Off.
        /// </summary>
        private Footswitch footswitch;

        /// <summary>
        ///  Key for the footswitch setting.
        /// </summary>
        private const int footswitchKey=0;

        /// <summary>
        ///  Indicates On/Off status of the effect.
        /// </summary>
        private LED led;

        /// <summary>
        ///  Synchornizes led with footswitch.
        /// </summary>
        private FootswitchLedSynchronizer synchronizer;

        #endregion

        #region delay_time

        /// <summary>
        ///  Constant storing maximal delay time of Delay
        /// </summary>
        private const double delayTimeMaximum = 2000;
        
        /// <summary>
        ///  Pot adjusting the delay time.
        /// </summary>
        private Pot DelayTime;

        /// <summary>
        ///  Delay time in miliseconds
        /// </summary>
        private int delayTime;

        /// <summary>
        ///  Key for the delay time parameter.
        /// </summary>
        private const int DelayTimeKey = 1;

        #endregion

        #region depth
        /// <summary>
        ///  Pot adjusting depth of the effect - dry/wet mix.
        /// </summary>
        private Pot Depth;

        /// <summary>
        ///  depth of the effect (dry/wet mix) 
        /// </summary>
        private double depth;

        /// <summary>
        ///  Key for the depth parameter.
        /// </summary>
        private const int DepthKey = 2;

        #endregion

        #region feedback

        /// <summary>
        ///  Pot adjusting Feedback
        /// </summary>
        private Pot Feedback;

        /// <summary>
        ///  Current feedback value.
        /// </summary>
        private double feedback;

        /// <summary>
        ///  Key for the feedback parameter.
        /// </summary>
        private const int FeedbackKey = 3;

        #endregion

        #endregion

        #region effect_properties
        // overridden preperties of base class Effect

        /// <summary>
        ///  Gets or sets sample rate of input signal
        /// </summary>
        /// <remarks>
        ///  It is necessary to adjust delay line, when changing sampling rate. The same time then means different count of samples. 
        /// </remarks>
        public override uint SampleRate
        {
            get
            {
                return base.SampleRate;
            }
            set
            {
                base.SampleRate = value;
                buffer = new VariableDelayLine(delayTimeMaximum,(int) sampleRate, 0);
            }
        }

        /// <summary>
        ///  Gets name of the effect.
        /// </summary>
        public override string Name
        {
            get
            {
                return "Delay v 2.0";
            }
        }

        #endregion

        #region constructor
        // constructor and initialization of the effect

        /// <summary>
        ///  Creates new instance of Delay.
        /// </summary>
        /// <remarks>
        ///  Created instanace has only initial settings. Sampling rate must be set to work properly.
        /// </remarks>
        public Delay()
        {
            // create new delay line
            buffer = new VariableDelayLine(delayTimeMaximum, 44100, 0);

            // initialize pots and appearance of the effect
            this.BackgroundImage = System.Drawing.Image.FromStream(new System.IO.MemoryStream(global::Effects.Recources.EffectBackgorunds.Delay));
            this.Size = this.BackgroundImage.Size;

            InitializePots();
            InitializeFootswitch();
        }

        /// <summary>
        ///  Initializes pots.
        /// </summary>
        private void InitializePots()
        {
            DelayTime = new Pot();
            DelayTime.MaxValue = Convert.ToInt32(delayTimeMaximum);
            DelayTime.MinValue = 0;
            DelayTime.ValueChanged += new PotValueChangedEventHandler(this.DelayTime_Changed);
            DelayTime.Location = new System.Drawing.Point(5, 43);
            //DelayTime.Size = new System.Drawing.Size(110, 110);
            DelayTime.Knob = PotKnob.ChickenKnobMiddle;
            this.Controls.Add(DelayTime);
            DelayTime.Reset();

            Depth = new Pot();
            Depth.MaxValue = 1;
            Depth.MinValue = 0;
            Depth.ValueChanged += new PotValueChangedEventHandler(this.Depth_Changed);
            Depth.Location = new System.Drawing.Point(220, 43);
            //Depth.Size = new System.Drawing.Size(110, 110);            
            Depth.Knob = PotKnob.ChickenKnobMiddle;
            this.Controls.Add(Depth);
            Depth.Reset();

            Feedback = new Pot();
            Feedback.MinValue = 0;
            Feedback.MaxValue = 1;
            Feedback.ValueChanged += new PotValueChangedEventHandler(this.Feedback_Changed);
            Feedback.Location = new System.Drawing.Point(85, 43);
            Feedback.Knob = PotKnob.ChickenKnobMiddle;
            this.Controls.Add(Feedback);
            Feedback.Reset();
        }

        /// <summary>
        ///  Initializes footswitch and led.
        /// </summary>
        private void InitializeFootswitch()
        {
            // footswitch
            footswitch = new Footswitch();
            switchedOn = true;
            footswitch.On = true;
            footswitch.Switched += new FootswitchSwitchedEventHandler(this.FootswitchSwitched);
            footswitch.Location = new System.Drawing.Point(185, 110);
            Controls.Add(footswitch);

            // led
            led = new LED();
            led.On = true;
            led.Location = new System.Drawing.Point(190, 40);
            Controls.Add(led);

            // synchronization
            synchronizer = new FootswitchLedSynchronizer(footswitch, led);
        }

        #endregion

        #region controls_event_handlers
        // handling events from controls

        /// <summary>
        ///  DelayTime pot event handler
        /// </summary>
        /// <param name="sender"> Object that has raised the event = DelayTime Pot </param>
        /// <param name="e"> Event arguments </param>
        /// <remarks>
        ///  Delay time is changed when called.
        /// </remarks>
        protected void DelayTime_Changed(object sender, PotValueChangedEventArgs e)
        {
            delayTime = e.NewValueInt;
            //buffer.DelayLength = Convert.ToInt32((double)delayTime / ((double)1000 / (double)sampleRate));
            buffer.DelayLength = Convert.ToInt32((double)delayTime / ((double)1000 / (double)sampleRate));
            //buffer = new Effects.Tools.Buffer(Convert.ToInt32((double)delayTime / ((double)1000 / (double)sampleRate)));

            // raising SettingsChanged event
            EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(DelayTimeKey, delayTime);
            if (this.SettingsChanged != null)
                SettingsChanged(this, changeArgs);
        }

        
        /// <summary>
        ///  Depth pot event handler.
        /// </summary>
        /// <param name="sender"> Object that has raised the event = Depth Pot </param>
        /// <param name="e"> Event arguments </param>
        protected void Depth_Changed(object sender, PotValueChangedEventArgs e)
        {
            depth = e.NewValue;

            // raising SettingChanged event
            EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(DepthKey, depth);
            if (this.SettingsChanged != null)
                SettingsChanged(this, changeArgs);
        }


        /// <summary>
        ///  Feedback pot event handler.
        /// </summary>
        /// <param name="sender"> Object that has raised the event = Feedback Pot </param>
        /// <param name="e"> Event arguments </param>
        protected void Feedback_Changed(object sender, PotValueChangedEventArgs e)
        {
            feedback = e.NewValue;
            this.buffer.Feedback = feedback;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(FeedbackKey, feedback);
            if (this.SettingsChanged != null)
                this.SettingsChanged(this, settings);
        }

        /// <summary>
        ///  Footswitch.Switched event handler. Turns effect On or Off accordingly to the event arguments.
        /// </summary>
        /// <param name="sender"> Object that has raised the event </param>
        /// <param name="args"> Event arguments </param>
        protected void FootswitchSwitched(object sender, FootswitchSwitchedEventArgs args)
        {
            this.switchedOn = args.On;

            EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(footswitchKey, switchedOn);
            if (SettingsChanged != null)
                this.SettingsChanged(this, changeArgs);
        }

        #endregion

        #region main_work

        /// <summary>
        ///  delay line, buffer simulating delay of the sound
        /// </summary>
        private Effects.Tools.VariableDelayLine buffer;

        /// <summary>
        ///  The key method of the effect.
        /// </summary>
        /// <param name="data"> Input signal </param>
        /// <returns> Modified signal </returns>
        public override double[] effect(double[] data)
        {
            if (switchedOn)
            {
                double[] delayed = new double[data.Length];
                for (int i = 0; i < data.Length; i++)
                {
                    // and gets delayed
                    delayed[i] = (1 - depth) * data[i] + depth * buffer.Step(data[i]);
                }

                return delayed;
            }
            else
            {
                // just pass data to the delay line, but return dry input
                for (int i = 0; i < data.Length; i++)
                    buffer.Step(data[i]);

                return data;
            }
        }

        /// <summary>
        ///  Reset method, clears delay line.
        /// </summary>
        /// <remarks>
        ///   When effect is reseted we create new buffer with same settings as the old one has had. This brings us clear delay line.
        /// </remarks>
        public override void Reset()
        {
            int delayLength = buffer.DelayLength;
            //buffer = new VariableLenghtBuffer(buffer.Length);
            buffer = new VariableDelayLine(delayTimeMaximum, (int)sampleRate, feedback);
            buffer.DelayLength = delayLength;
        }

        #endregion

        #region settings
        // getting and setting the settings of delay

        /// <summary>
        ///  Raised when any parameter of effect is changed.
        /// </summary>
        public override event EffectSettingsChangedDelegate SettingsChanged;

        /// <summary>
        ///  Sets settings 
        /// </summary>
        /// <param name="e"> Settings that are to be changed </param>
        public override void SetSetting(EffectSettingsChangedEventArgs e)
        {
            int key = (int)e.Key;
            if (key == DepthKey)
            {
                depth = (double)e.Value;
                Depth.SetValue(depth);
                return;
            }
            
            if (key == DelayTimeKey)
            {
                delayTime = (int)e.Value;
                buffer.DelayLength = Convert.ToInt32((double)delayTime / ((double)1000 / (double)sampleRate));
                DelayTime.SetValue(delayTime);
                return;
            }
            
            if (key == FeedbackKey)
            {
                feedback = (double)e.Value;
                buffer.Feedback = feedback;
                Feedback.SetValue(feedback);
                return;
            }

            if (key == footswitchKey)
            {
                switchedOn = (bool)e.Value;
                synchronizer.SetValue(switchedOn);
                return;
            }
        }

        // get initial settings implementation
        /// <summary>
        ///  Gets all current settings.
        /// </summary>
        /// <returns> All current settings </returns>
        public override object[] GetAllSettings()
        {
            object[] settings = new object[4];
            settings[0] = switchedOn;
            settings[1] = depth;
            settings[2] = delayTime;
            settings[3] = feedback;
            return settings;
        }

        /// <summary>
        ///  Sets all settings.
        /// </summary>
        /// <param name="settings"> Settings to set. This argument should be taken from GetAllSettings previously </param>
        public override void SetAllSettings(object[] settings)
        {
            object[] sets = (object[])settings;
            
            switchedOn = (bool)sets[0];
            synchronizer.SetValue(switchedOn);

            depth = (double)sets[1];
            Depth.SetValue(depth);

            delayTime = (int)sets[2];
            buffer.DelayLength = delayTime;
            DelayTime.SetValue(delayTime);

            feedback = (double)sets[3];
            buffer.Feedback = feedback;
            Feedback.SetValue(feedback);
        }


        /// <summary>
        ///  Gets array containing all effect's parameters.
        /// </summary>
        /// <returns> Array containing description fo all parameters </returns>
        public override EffectParam[] GetParameters()
        {
            
            EffectParam[] parameters = new EffectParam[4];

            ListItem[] footswitchState = new ListItem[2];
            footswitchState[0] = new ListItem("Turn on", true);
            footswitchState[1] = new ListItem("Turn off", false);
            parameters[0] = new EffectParam(footswitchKey, "ON/OFF", footswitchState);

            parameters[1] = new EffectParam(DepthKey, "Depth", 0, 0.5);
            parameters[2] = new EffectParam(DelayTimeKey, "Delay time", 0, 2000);
            parameters[3] = new EffectParam(FeedbackKey, "Feedback", 0F, 1F);

            return parameters;
        }

        #endregion
    }
}
